package timbap.visualizer.api.plugin.poscloud;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import processing.core.PApplet;
import processing.core.PImage;
import timbap.visualizer.api.context.PluginContext;
import timbap.visualizer.api.model.PlaylistEntry;
import timbap.visualizer.api.plugin.AbstractPlugin;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

/**
 * The <code>AbstractPosCloudPlugin</code> provides a common base
 * implementation of the <code>PosCloudPlugin</code> interface that
 * should be used by all positioning cloud plugins.
 *
 * It provides an implementation of the <code>draw()</code> method
 * that applies a common transformation of the coordinate system and
 * delegates the drawing of the elements to the unimplemented
 * <code>drawPosCloudElement(T, boolean)</code> method.
 *
 * It also constructs a data structure for the elements leaving the
 * individual creation abstract. This way, a generic sorting mechanism
 * can be provided and performed in the right moment.
 *
 * Plugins extending this abstract class must use an element type
 * that implements <code>java.lang.Comparable</code> though.
 *
 * @author  Andreas Pabst
 * @see     timbap.visualizer.api.plugin.poscloud.PosCloudPlugin
 * @see     timbap.visualizer.api.plugin.AbstractPlugin
 */
public abstract class AbstractPosCloudPlugin<E extends Comparable<? super E>>
                        extends AbstractPlugin implements PosCloudPlugin<E> {

	private static final Log LOG = LogFactory.getLog(AbstractPosCloudPlugin.class);
    private static final int FIXED_XSCALE_FACTOR = 1;
    private static final String ARROW_BUTTON_LEFT_IMAGE = "arrowbutton_left_blurred.png";
    private static final String ARROW_BUTTON_RIGHT_IMAGE = "arrowbutton_right_blurred.png";

    
    Map<PlaylistEntry, E> posCloudElementMap = new HashMap<PlaylistEntry, E>();
    Map<E, List<PlaylistEntry>> reversePosCloudElementMap;
    List<E> sortedPosCloudElements;

    boolean elementWidthFixed;
    boolean elementDisplayCentered;

    private PImage arrowButtonLeft;
    private PImage arrowButtonRight;

    public abstract void drawPosCloudElement(E pPosCloudElement, boolean pIsSelected);
    
    public abstract E createPosCloudElement(PlaylistEntry pPlaylistEntry);

    @Override
    public void activate() {
        Collections.sort(getContext().getPlaylistManager().getPlaylist(), getPlaylistEntryComparator());
        if (LOG.isDebugEnabled()) {
        	LOG.debug("Sorted playlist according to posCloudPlugin " + this + ".");
        	LOG.debug("Resulting order is: ");
	        for (PlaylistEntry tSortedPlaylistEntry : getContext().getPlaylistManager().getPlaylist()) {
	        	E tPosCloudElement = posCloudElementMap.get(tSortedPlaylistEntry);
	        	LOG.debug(tPosCloudElement.toString() + " <- " + tSortedPlaylistEntry.toString());
	        }
        }
    }

    @Override
    public void init(PluginContext pPluginContext) {
        super.init(pPluginContext);

        elementWidthFixed = false;
        elementDisplayCentered = true;
        initPosCloudElementMap();
        arrowButtonLeft = getContext().getP5Applet().loadImage(ARROW_BUTTON_LEFT_IMAGE);
        arrowButtonRight = getContext().getP5Applet().loadImage(ARROW_BUTTON_RIGHT_IMAGE);
    }

    /**
     * initializes the positioning cloud element map 
     */
    public void initPosCloudElementMap() {
        Map<PlaylistEntry, E> tPosCloudElementMap = new HashMap<PlaylistEntry, E>();
        for (PlaylistEntry tPlaylistEntry : getContext().getPlaylistManager().getPlaylist()) {
            E tPosCloudElement = createPosCloudElement(tPlaylistEntry);
            tPosCloudElementMap.put(tPlaylistEntry, tPosCloudElement);
        }
        addToPosCloudElementMap(tPosCloudElementMap);
    }

    public Map<PlaylistEntry, E> getPosCloudElementMap() {
        return posCloudElementMap;
    }

    /**
     * adds all entries of a given positioning cloud element map
     * to the current positioning cloud element map
     * 
     * @param pPosCloudElementMap	the positioning cloud element map to add
     */
    public void addToPosCloudElementMap(Map<PlaylistEntry, E> pPosCloudElementMap) {
        posCloudElementMap.putAll(pPosCloudElementMap);
        reversePosCloudElementMap = new HashMap<E, List<PlaylistEntry>>();
        for (PlaylistEntry tPlaylistEntry: posCloudElementMap.keySet()) {
            E tPosCloudElement = posCloudElementMap.get(tPlaylistEntry);
            List<PlaylistEntry> tAllMappedPlaylistEntries = reversePosCloudElementMap.get(tPosCloudElement);
            if (tAllMappedPlaylistEntries != null) {
                tAllMappedPlaylistEntries = new ArrayList<PlaylistEntry>(tAllMappedPlaylistEntries);
                tAllMappedPlaylistEntries.add(tPlaylistEntry);
            } else {
                tAllMappedPlaylistEntries = Collections.singletonList(tPlaylistEntry);
            }
            reversePosCloudElementMap.put(tPosCloudElement, tAllMappedPlaylistEntries);
        }
        sortedPosCloudElements = new ArrayList<E>(new HashSet<E>(posCloudElementMap.values()));
        sortPosCloudElements();
    }

    public Collection<E> getPosCloudElements() {
        return sortedPosCloudElements;
    }

    public E findPosCloudElement(PlaylistEntry pPlaylistEntry) {
        return posCloudElementMap.get(pPlaylistEntry);
    }

    public void sortPosCloudElements() {
        Collections.sort(sortedPosCloudElements);
        // TODO: replace this with a transformation of the single elements
        Collections.reverse(sortedPosCloudElements);
        if (LOG.isDebugEnabled()) {
        	LOG.debug("Sorted posCloudElements of posCloudPlugin " + this + ".");
        	LOG.debug("Resulting order is: ");
	        for (E tPosCloudElement: sortedPosCloudElements) {
	        	LOG.debug(tPosCloudElement);
	        }
        }
    }
    
    /**
     * determines whether the width of the positioning
     * cloud elements is fixed or adaptable.
     * 
     * @return	true if the element width is fixed, false otherwise
     */
    public boolean isElementWidthFixed() {
        return elementWidthFixed;
    }

    /**
     * sets whether the element width is fixed
     * 
     * @param pElementWidthFixed whether the element width should be fixed
     */
    public void setElementWidthFixed(boolean pElementWidthFixed) {
        this.elementWidthFixed = pElementWidthFixed;
    }

    /**
     * determines whether the positioning
     * cloud elements are displayed centered.
     * 
     * @return	true if the element display is centered, false otherwise
     */
    public boolean isElementDisplayCentered() {
        return elementDisplayCentered;
    }

    /**
     * sets whether the element display is centered
     * 
     * @param pElementDisplayCentered whether the element display should be centered
     */
    public void setElementDisplayCentered(boolean pElementDisplayCentered) {
        this.elementDisplayCentered = pElementDisplayCentered;
    }    


    public void draw() {
        
        getContext().getP5Applet().pushMatrix();
        getContext().getP5Applet().translate(
                getContext().getCfg().getPosCloudXPos() + getContext().getCfg().getScreenWidth() / 10,
                getContext().getCfg().getPosCloudYPos() + getContext().getCfg().getScreenHeight() / 10);
        getContext().getP5Applet().rotate(PApplet.TWO_PI / 360 * getContext().getCfg().getPosCloudRotation());
        
        int tOverallRecordCount = getContext().getPlaylistManager().getPlaylistSize();
        int tSummedUpRecordCount = 0;

        float tDisplayRange = (getContext().getCfg().getScreenHeight() - 50)
                            * getContext().getCfg().getPosCloudScaleFactor();
        PlaylistEntry tSelectedPlaylistEntry = getContext().getRecordItemManager().getSelectedRecordItem().getPlaylistEntry();
        
        for (E tPosCloudElement: getPosCloudElements()) {
            getContext().getP5Applet().pushMatrix();

            int tRecordCount = 0;
            List<PlaylistEntry> tRecordsRepresentedByThisElement = reversePosCloudElementMap.get(tPosCloudElement);
            if (tRecordsRepresentedByThisElement != null) {
                tRecordCount = tRecordsRepresentedByThisElement.size();
            }
            float tProportionalRecordCount = (float)tRecordCount / tOverallRecordCount;  
            float tYTranslationFactor = ((float)tSummedUpRecordCount / tOverallRecordCount) * tDisplayRange;
            float tYScaleFactor = tProportionalRecordCount / 35 * tDisplayRange;
            float tXScaleFactor = (isElementWidthFixed()) ? FIXED_XSCALE_FACTOR
                    : tProportionalRecordCount / 35 * tDisplayRange;

            float tXTranslationFactor = 0;
            if (isElementDisplayCentered()) {
                tXTranslationFactor = -tProportionalRecordCount * 50;
            }

            boolean tIsSelected = tPosCloudElement.equals(posCloudElementMap.get(tSelectedPlaylistEntry));
            
            getContext().getP5Applet().translate(tXTranslationFactor, tYTranslationFactor);
            getContext().getP5Applet().scale(tXScaleFactor, tYScaleFactor);
            
            drawPosCloudElement(tPosCloudElement, tIsSelected);

            tSummedUpRecordCount += tRecordCount;
            
            getContext().getP5Applet().popMatrix();
        }
        
        getContext().getP5Applet().translate(0, tDisplayRange * 1.1f);
        getContext().getP5Applet().scale(0.25f);
        getContext().getP5Applet().image(arrowButtonLeft, -90, 0);
        getContext().getP5Applet().image(arrowButtonRight, 10, 0);
        getContext().getP5Applet().popMatrix();
    }
    
    

}
